{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ec3e8673",
   "metadata": {},
   "source": [
    "## 5.3 前向传播和反向传播"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "376e0bdc",
   "metadata": {},
   "source": [
    "前面讲了多层感知机和相关代码实现，也给出了关于隐藏层的数学表示。这一节我们着重看一下，隐藏在深度学习框架之下的模型参数究竟起到了什么作用。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5613dce0",
   "metadata": {},
   "source": [
    "### 5.3.1 什么是前向传播"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d7838ff",
   "metadata": {},
   "source": [
    "前向传播（Forward Propagation）是指在一个神经网络中，从输入层到输出层的信息传递过程。具体来说就是，将上一层的输出作为下一层的输入，并计算下一层的输出，一直到运算到输出层为止。\n",
    "\n",
    "前面我们讲过，在多层感知机中，隐藏层的数学表示为：\n",
    "\n",
    "$$\\mathbf{h} = f(\\mathbf{W}_h\\mathbf{h}_{prev} + \\mathbf{b}_h)$$\n",
    "\n",
    "其中，$\\mathbf{h}$ 是隐藏层的输出，$\\mathbf{h}_{prev}$ 是上一层（通常是输入层）的输出，$\\mathbf{W}_h$ 是隐藏层的权值矩阵，$\\mathbf{b}_h$ 是隐藏层的偏置向量，$f(\\cdot)$ 是激活函数。\n",
    "\n",
    "那么假设给定一个两层的神经网络，设输入为 $\\mathbf{x}$，则前向传播的过程可以表示为：\n",
    "\n",
    "$$ \\mathbf{z}^{(1)} = \\mathbf{W}^{(1)}\\mathbf{x} + \\mathbf{b}^{(1)} $$\n",
    "\n",
    "$$ \\mathbf{h}^{(1)} = f^{(1)}(\\mathbf{z}^{(1)}) $$\n",
    "\n",
    "$$ \\mathbf{z}^{(2)} = \\mathbf{W}^{(2)}\\mathbf{h}^{(1)} + \\mathbf{b}^{(2)} $$\n",
    "\n",
    "$$ \\mathbf{h}^{(2)} = f^{(2)}(\\mathbf{z}^{(2)}) $$\n",
    "\n",
    "其中，$\\mathbf{z}^{(1)}$ 和 $\\mathbf{z}^{(2)}$ 分别表示第一层和第二层的线性输出，$\\mathbf{h}^{(1)}$ 和 $\\mathbf{h}^{(2)}$ 分别表示第一层和第二层的激活输出，$\\mathbf{W}^{(1)}$ 和 $\\mathbf{W}^{(2)}$ 分别表示第一层和第二层的权重矩阵，$\\mathbf{b}^{(1)}$ 和 $\\mathbf{b}^{(2)}$ 分别表示第一层和第二层的偏置向量，$f^{(1)}(\\cdot)$ 和 $f^{(2)}(\\cdot)$ 分别表示第一层和第二层的激活函数。\n",
    "\n",
    "如果指定了输入 $\\mathbf{x}$ 和所有的权重和偏置，那么可以通过上述公式，可以计算出第一层的线性输出 $\\mathbf{z}^{(1)}$，然后通过激活函数 $f^{(1)}(\\cdot)$ 计算出第一层的激活输出 $\\mathbf{h}^{(1)}$。接着，计算出第二层的线性输出 $\\mathbf{z}^{(2)}$，然后通过激活函数 $f^{(2)}(\\cdot)$ 计算出第二层的激活输出 $\\mathbf{h}^{(2)}$。\n",
    "\n",
    "因此，通过前向传播的过程，可以计算出两层神经网络的输出 $\\mathbf{h}^{(2)}$。这个输出就是神经网络对输入 $\\mathbf{x}$ 的预测结果。通常，在神经网络的训练过程中，会使用一个损失函数来衡量预测结果和真实结果的差距。比如常用的均方误差（mean squared error, MSE）损失函数的形式如下：\n",
    "\n",
    "$$ MSE = \\frac{1}{n}\\sum_{i=1}^{n}(y_{i} - \\hat{y}_{i})^{2} $$\n",
    "\n",
    "其中，$y_{i}$ 是第 $i$ 个样本的真实值，$\\hat{y}_{i}$ 是模型预测的输出值，$n$ 是样本数量。\n",
    "\n",
    "因此，通过前向传播的过程，我们可以得到神经网络对输入的预测结果，并使用损失函数计算出预测结果和真实结果的差距。这个信息可以用来评估神经网络的性能。\n",
    "\n",
    "通常，我们希望通过训练使得损失函数的值尽可能的小。因此，需要找到一种方法来有效地更新权重和偏置，使得损失函数的值能够得到有效的减小。这个方法就是接下来要讲的反向传播算法以及后续会讲到的梯度下降算法，这也是深度学习的核心思想。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aee01059",
   "metadata": {},
   "source": [
    "### 5.3.2 什么是反向传播"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2557c4c",
   "metadata": {},
   "source": [
    "反向传播 (Back Propagation) 本质上指的是计算神经⽹络每一层参数梯度的⽅法。利用链式法则逐层求出损失函数对各个神经元权重和偏置的偏导数，构成损失函数对权值和偏置向量的梯度，作为修改权值的依据。\n",
    "\n",
    "假设有一个神经网络有 $n$ 层，其中第 $l$ 层的激活输出为 $\\mathbf{h}^{(l)}$，线性输出为 $\\mathbf{z}^{(l)}$，权重为 $\\mathbf{W}^{(l)}$，偏置为 $\\mathbf{b}^{(l)}$。损失函数为 $L(\\mathbf{y}, \\hat{\\mathbf{y}})$。\n",
    "\n",
    "那么基于链式法则，反向传播算法的梯度计算公式如下：\n",
    "\n",
    "$$ \\frac{\\partial L}{\\partial \\mathbf{h}^{(l)}} = \\frac{\\partial L}{\\partial \\mathbf{h}^{(l+1)}} \\cdot \\frac{\\partial \\mathbf{h}^{(l+1)}}{\\partial \\mathbf{z}^{(l+1)}} \\cdot \\frac{\\partial \\mathbf{z}^{(l+1)}}{\\partial \\mathbf{h}^{(l)}} $$\n",
    "\n",
    "$$ \\frac{\\partial L}{\\partial \\mathbf{z}^{(l)}} = \\frac{\\partial L}{\\partial \\mathbf{h}^{(l+1)}} \\cdot \\frac{\\partial \\mathbf{h}^{(l+1)}}{\\partial \\mathbf{z}^{(l+1)}} \\cdot \\frac{\\partial \\mathbf{z}^{(l+1)}}{\\partial \\mathbf{h}^{(l)}} \\cdot \\frac{\\partial \\mathbf{h}^{(l)}}{\\partial \\mathbf{z}^{(l)}}$$\n",
    "\n",
    "$$ \\frac{\\partial L}{\\partial \\mathbf{W}^{(l)}} = \\frac{\\partial L}{\\partial \\mathbf{h}^{(l+1)}} \\cdot \\frac{\\partial \\mathbf{h}^{(l+1)}}{\\partial \\mathbf{z}^{(l+1)}} \\cdot \\frac{\\partial \\mathbf{z}^{(l+1)}}{\\partial \\mathbf{h}^{(l)}} \\cdot \\frac{\\partial \\mathbf{h}^{(l)}}{\\partial \\mathbf{z}^{(l)}} \\cdot \\frac{\\partial \\mathbf{z}^{(l)}}{\\partial \\mathbf{W}^{(l)}} $$\n",
    "\n",
    "$$ \\frac{\\partial L}{\\partial \\mathbf{b}^{(l)}} = \\frac{\\partial L}{\\partial \\mathbf{h}^{(l+1)}} \\cdot \\frac{\\partial \\mathbf{h}^{(l+1)}}{\\partial \\mathbf{z}^{(l+1)}} \\cdot \\frac{\\partial \\mathbf{z}^{(l+1)}}{\\partial \\mathbf{h}^{(l)}} \\cdot \\frac{\\partial \\mathbf{h}^{(l)}}{\\partial \\mathbf{z}^{(l)}} \\cdot \\frac{\\partial \\mathbf{z}^{(l)}}{\\partial \\mathbf{b}^{(l)}} $$\n",
    "\n",
    "其中，$\\frac{\\partial L}{\\partial \\mathbf{h}^{(l+1)}}$ 和 $\\frac{\\partial L}{\\partial \\mathbf{h}^{(l)}}$ 是损失函数对输出的梯度，$\\frac{\\partial \\mathbf{h}^{(l)}}{\\partial \\mathbf{z}^{(l)}}$ 是当前层激活函数的导数，$\\frac{\\partial \\mathbf{z}^{(l+1)}}{\\partial \\mathbf{h}^{(l)}}$ 是下一层的权重矩阵。\n",
    "\n",
    "替换简化后可得到：\n",
    "\n",
    "$$ \\frac{\\partial L}{\\partial \\mathbf{h}^{(l)}} = \\frac{\\partial L}{\\partial \\mathbf{h}^{(l+1)}} \\cdot \\frac{\\partial \\mathbf{h}^{(l+1)}}{\\partial \\mathbf{z}^{(l+1)}} \\cdot \\frac{\\partial \\mathbf{z}^{(l+1)}}{\\partial \\mathbf{h}^{(l)}} $$\n",
    "\n",
    "$$ \\frac{\\partial L}{\\partial \\mathbf{z}^{(l)}} = \\frac{\\partial L}{\\partial \\mathbf{h}^{(l)}} \\cdot \\frac{\\partial \\mathbf{h}^{(l)}}{\\partial \\mathbf{z}^{(l)}} $$\n",
    "\n",
    "$$ \\frac{\\partial L}{\\partial \\mathbf{W}^{(l)}} = \\frac{\\partial L}{\\partial \\mathbf{z}^{(l)}} \\cdot \\frac{\\partial \\mathbf{z}^{(l)}}{\\partial \\mathbf{W}^{(l)}} $$\n",
    "\n",
    "$$ \\frac{\\partial L}{\\partial \\mathbf{b}^{(l)}} = \\frac{\\partial L}{\\partial \\mathbf{z}^{(l)}} \\cdot 1 $$\n",
    "\n",
    "在反向传播的过程中，我们需要从输出层开始，逐层往输入层计算梯度。也就是说从最后一层开始，逐层往第一层计算梯度。\n",
    "\n",
    "反向传播算法的梯度计算公式是基于链式法则来推导的，是对神经网络各层间关系的数学描述。在进行梯度计算时，我们可以使用反向传播算法的梯度计算公式来逐层计算神经网络中各层的梯度。然后，就可以使用梯度下降法或其他优化算法来更新神经网络的参数，使得神经网络的性能逐步达到最优。\n",
    "\n",
    "同学们如果看到这堆公式直接头晕了也不要担心，在反向传播算法的过程中，可以使用深度学习框架的自动微分工具来计算梯度，这样可以大大减少手动推导梯度的工作量。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a2be3a6d",
   "metadata": {},
   "source": [
    "### 5.3.3 简单实例"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fe99db84",
   "metadata": {},
   "source": [
    "前面的公式推导可能让不少同学看晕了，别着急，看一个具体的例子就会明白，所谓的公式只是一种严谨表达，其背后的含义可能并不难理解。\n",
    "\n",
    "假定一个最简单的网络，包含两个隐层，每层只有一个神经元，其结构如下图所示。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "97c8d2e7",
   "metadata": {},
   "source": [
    "<img src=\"../images/5-3-1.png\" width=\"100%\"></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28affda9",
   "metadata": {},
   "source": [
    "这里对符号也做了一点适当的简化，其中 $x$ 是输入， $w$ 和 $b$ 表示隐藏层的权重和偏置， $z$ 表示线性输出， $h$ 表示经过激活函数之后的输出，$y$ 表示输出值。\n",
    "\n",
    "为便于同学们理解和计算，假定这里的激活函数为 $f(x)=x$ ，即 $h=f(z)=z$。\n",
    "\n",
    "那么对于这个例子，其前向传播的过程为：\n",
    "\n",
    "$$ h_1=f(z_1)=z_1=w_1\\cdot x+b_1 $$\n",
    "\n",
    "$$ y_{predict}=h_2=f(z_2)=z_2=w_2\\cdot h_1+b_2=w_2\\cdot (w_1\\cdot x+b_1)+b_2 $$\n",
    "\n",
    "同样为方便求导，这里定义损失函数为：\n",
    "\n",
    "$$ Loss=\\frac{1}{2}(y_{real}-y_{predict})^2 $$\n",
    "\n",
    "然后得到其反向传播的过程：\n",
    "\n",
    "$$ \\frac{\\partial L}{\\partial y} =y_{predict}-y_{real} $$\n",
    "\n",
    "$$ \\frac{\\partial L}{\\partial w_2} =\\frac{\\partial L}{\\partial y} \\cdot \\frac{\\partial y}{\\partial h_2} \\cdot \\frac{\\partial h_2}{\\partial z_2} \\cdot \\frac{\\partial z_2}{\\partial w_2} =(y_{predict}-y_{real})\\cdot 1\\cdot 1\\cdot h_1 =(y_{predict}-y_{real})\\cdot h_1  $$\n",
    "\n",
    "$$ \\frac{\\partial L}{\\partial b_2} =\\frac{\\partial L}{\\partial y} \\cdot \\frac{\\partial y}{\\partial h_2} \\cdot \\frac{\\partial h_2}{\\partial z_2} \\cdot \\frac{\\partial z_2}{\\partial b_2} =(y_{predict}-y_{real})\\cdot 1\\cdot 1\\cdot 1 =y_{predict}-y_{real}  $$\n",
    "\n",
    "$$ \\frac{\\partial L}{\\partial h_1} =\\frac{\\partial L}{\\partial y} \\cdot \\frac{\\partial y}{\\partial h_2} \\cdot \\frac{\\partial h_2}{\\partial z_2} \\cdot \\frac{\\partial z_2}{\\partial h_1} =(y_{predict}-y_{real})\\cdot 1\\cdot 1\\cdot w_2=(y_{predict}-y_{real})\\cdot w_2  $$\n",
    "\n",
    "$$ \\frac{\\partial L}{\\partial w_1} =\\frac{\\partial L}{\\partial h_1} \\cdot \\frac{\\partial h_1}{\\partial z_1} \\cdot \\frac{\\partial z_1}{\\partial w_1} =(y_{predict}-y_{real})\\cdot w_2\\cdot 1\\cdot x =(y_{predict}-y_{real})\\cdot w_2\\cdot x  $$\n",
    "\n",
    "$$ \\frac{\\partial L}{\\partial b_1} =\\frac{\\partial L}{\\partial h_1} \\cdot \\frac{\\partial h_1}{\\partial z_1} \\cdot \\frac{\\partial z_1}{\\partial b_1} =(y_{predict}-y_{real})\\cdot w_2\\cdot 1\\cdot 1 =(y_{predict}-y_{real})\\cdot w_2  $$\n",
    "\n",
    "至此，隐藏层权重和偏置项的梯度就都计算出来了。希望同学们通过这个简单例子了解前向传播和反向传播的含义，其实并没有想象中那么可怕。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23ce1463",
   "metadata": {},
   "source": [
    "**梗直哥提示：如果你想了解更多关于深度学习的内容，欢迎入群学习（加V: gengzhige99）**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51e045e8",
   "metadata": {},
   "source": [
    "[Next 5-4 常见问题及对策](./5-4%20常见问题及对策.ipynb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dfec4d32",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
